using System;
using System.Text;

namespace PDUConverter
{
    /// <summary>
    /// The relative validity period gives the length of the validity period
    /// counted from when the SMS-SUBMIT is received by the SC.
    /// </summary>
    public class RelativeValidityPeriod : ValidityPeriod
    {
        private byte value;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" />.
        /// </summary>
        /// <param name="value">The byte value of the validity period. Use this
        /// if you have already calculated the validity yourself.</param>
        public RelativeValidityPeriod(byte value)
        {
            this.value = value;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" />.
        /// </summary>
        /// <param name="period">The validity period.</param>
        /// <remarks>
        /// There are some rules to note:
        /// <list type="bullet">
        /// <item><description>
        /// The smallest validity period is 5 minutes, 63 weeks the largest.
        /// </description></item>
        /// <item><description>
        /// Periods between 5 minutes and 12 hours can be specified in 5 minute steps.
        /// </description></item>
        /// <item><description>
        /// Periods between 12h30min and 24 hours can be specified in 30 minute steps.
        /// </description></item>
        /// <item><description>
        /// Periods between two days and 30 days can be specified in 1 day steps.
        /// </description></item>
        /// <item><description>
        /// Periods between 5 weeks and 63 weeks can be specified in 1 week (=7 days) steps.
        /// </description></item>
        /// </list>
        /// </remarks>
        /// <exception cref="T:System.ArgumentException">Validity timespan is invalid.</exception>
        public RelativeValidityPeriod(TimeSpan period)
        {
            for (byte i = 0; i <= 0xff; i = (byte) (i + 1))
            {
                if (ToTimeSpan(i).CompareTo(period) == 0)
                {
                    this.value = i;
                    return;
                }
            }
            throw new ArgumentException("Invalid validity timespan.");
        }

        private void AppendIfNonzero(StringBuilder str, int val, string suffix)
        {
            if (val > 0)
            {
                if (str.Length != 0)
                {
                    str.Append(" ");
                }
                str.Append(val.ToString());
                str.Append(suffix);
            }
        }

        /// <summary>
        /// Converts the value of the specified <see cref="T:System.TimeSpan" /> structure into a <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" />.
        /// </summary>
        /// <param name="ts">A <see cref="T:System.TimeSpan" /> structure.</param>
        /// <returns>A new <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> object representing the value of the <see cref="T:System.TimeSpan" /> structure.</returns>
        public static explicit operator RelativeValidityPeriod(TimeSpan ts)
        {
            return new RelativeValidityPeriod(ts);
        }

        /// <summary>
        /// Converts the value of the specified instance into a byte.
        /// </summary>
        /// 
        /// <summary>
        /// Converts the value of the specified <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> instance into a <see cref="T:System.Byte" />.
        /// </summary>
        /// <param name="v">A <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> object.</param>
        /// <returns>A new <see cref="T:System.Byte" /> value representing the object's value.</returns>
        public static implicit operator byte(RelativeValidityPeriod v)
        {
            return v.ToByte();
        }

        /// <summary>
        /// Converts the value of the specified <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> instance into a <see cref="T:System.TimeSpan" /> structure.
        /// </summary>
        /// <param name="v">A <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> object.</param>
        /// <returns>A new <see cref="T:System.TimeSpan" /> structure representing the object's value.</returns>
        public static implicit operator TimeSpan(RelativeValidityPeriod v)
        {
            return v.ToTimeSpan();
        }

        /// <summary>
        /// Converts the specified <see cref="T:System.Byte" /> value into a <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> object.
        /// </summary>
        /// <param name="b">A <see cref="T:System.Byte" /> value.</param>
        /// <returns>A new <see cref="T:GsmComm.PduConverter.RelativeValidityPeriod" /> object whose value represents the <see cref="T:System.Byte" /> value.</returns>
        public static implicit operator RelativeValidityPeriod(byte b)
        {
            return new RelativeValidityPeriod(b);
        }

        /// <summary>
        /// Returns the byte equivalent of this instance.
        /// </summary>
        /// <returns>The byte value.</returns>
        public byte ToByte()
        {
            return this.value;
        }

        /// <summary>
        /// Returns the string equivalent of this instance.
        /// </summary>
        public override string ToString()
        {
            TimeSpan span = this.ToTimeSpan();
            if (span.TotalHours == 24.0)
            {
                return "24h";
            }
            StringBuilder str = new StringBuilder();
            this.AppendIfNonzero(str, span.Days, "d");
            this.AppendIfNonzero(str, span.Hours, "h");
            this.AppendIfNonzero(str, span.Minutes, "m");
            this.AppendIfNonzero(str, span.Seconds, "s");
            this.AppendIfNonzero(str, span.Milliseconds, "ms");
            return str.ToString();
        }

        /// <summary>
        /// Returns the TimeSpan equivalent of this instance.
        /// </summary>
        /// <returns>The TimeSpan value.</returns>
        public TimeSpan ToTimeSpan()
        {
            return ToTimeSpan(this.value);
        }

        private static TimeSpan ToTimeSpan(byte value)
        {
            if ((value >= 0) && (value <= 0x8f))
            {
                return new TimeSpan(0, (value + 1) * 5, 0);
            }
            if ((value >= 0x90) && (value <= 0xa7))
            {
                return new TimeSpan(12, (value - 0x8f) * 30, 0);
            }
            if ((value >= 0xa8) && (value <= 0xc4))
            {
                return new TimeSpan(value - 0xa6, 0, 0, 0);
            }
            if ((value >= 0xc5) && (value <= 0xff))
            {
                return new TimeSpan((value - 0xc0) * 7, 0, 0, 0);
            }
            return TimeSpan.Zero;
        }
    }
}